package com.marketlive.entity;

/*
(C) Copyright MarketLive. 2006. All rights reserved.
MarketLive is a trademark of MarketLive, Inc.
Warning: This computer program is protected by copyright law and international treaties.
Unauthorized reproduction or distribution of this program, or any portion of it, may result
in severe civil and criminal penalties, and will be prosecuted to the maximum extent
possible under the law.
*/

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.LockMode;
import org.hibernate.CacheMode;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.marketlive.entity.IAttributePage;
import org.marketlive.entity.IEntity;
import org.marketlive.entity.IEntityHome;
import org.marketlive.entity.IPrimaryKey;
import org.marketlive.entity.attributes.IAttributeHome;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import java.sql.Timestamp;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * The default implementation of <code>IEntityHome</code>. This is a Hibernate and Spring implementation.
 * It includes support for EntityDescriptor data. This is the base class for all Entity Home objects.
 */
public abstract class EntityHome extends HibernateDaoSupport implements IEntityHome {
    /**
     * Logger.
     */
    private static Log log = LogFactory.getLog(EntityHome.class);
    /**
     * Class of the entity managed by this home.
     */
    private final Class entityClass;
    /**
     * Type of the entity managed by this home.
     */
    private final String entityType;
    /**
     * Page definitions for entities managed by this home.  Keyed on page name.
     */
    private Map<String, Class> pageDefinitions = new HashMap<String, Class>();
    /**
     * Localized page definitions for entities managed by this home.  Keyed on page name.
     */
    private Map<String, Class> localizedPageDefinitions = new HashMap<String, Class>();

    /**
     * Constructor with required setup information. Must be called from the
     * constructor of each subclass.
     *
     * @param entityType  the type of entity managed by this home
     * @param entityClass the class that implements the entity managed by this home
     */
    public EntityHome(final String entityType, final Class entityClass) {
        this.entityType = entityType;
        this.entityClass = entityClass;
    }

    /**
     * Sets the attribute page definitions known to this <code>EntityHome</code>.
     * Intended for use by Hibernate.
     *
     * @param pageDefinitions the page classes known to this home, keyed on page name
     */
    public void setPageDefinitions(Map<String, String> pageDefinitions) {
        this.pageDefinitions = createClassDefinitions(pageDefinitions);
    }
    
    /**
     * Sets the localized attribute page definitions known to this <code>EntityHome</code>.
     * Intended for use by Hibernate.
     *
     * @param localizedPageDefinitions the page classes known to this home, keyed on page name
     */
    public void setLocalizedPageDefinitions(Map<String, String> localizedPageDefinitions) {
        this.localizedPageDefinitions = createClassDefinitions(localizedPageDefinitions);
    }

    /**
     * Converts collection of class names to collection of Class objects.
     * @param classNameDefinitions
     * @return Map of Class objects keyed with page name
     */
    private Map<String, Class> createClassDefinitions(Map<String, String> classNameDefinitions) {
        Map<String, Class> convertedDefinitions = new HashMap<String, Class>();
        try {
            for (String key : classNameDefinitions.keySet()) {
                Class clazz = Class.forName(classNameDefinitions.get(key));
                if (!(IAttributePage.class.isAssignableFrom(clazz))) {
                    throw new RuntimeException("Page must implement IAttributePage interface: " + clazz.getName());
                }
                convertedDefinitions.put(key, clazz);
            }
        } catch (ClassNotFoundException e) {
            log.error("Class not found for definition of available attribute pages: ", e);
        }
        return convertedDefinitions;
    }
    

    /**
     * {@inheritDoc}
     */
    public String getEntityType() {
        return entityType;
    }

    /**
     * {@inheritDoc}
     */
    public void definePage(final String name, final Class clazz) {
        if (!(IAttributePage.class.isAssignableFrom(clazz))) {
            throw new RuntimeException("Page must implement IAttributePage interface: " + clazz.getName());
        }

        this.pageDefinitions.put(name, clazz);
    }

    /**
     * {@inheritDoc}
     */
    public void defineLocalizedPage(final String name, final Class clazz) {
        if (!(IAttributePage.class.isAssignableFrom(clazz))) {
            throw new RuntimeException("Page must implement IAttributePage interface: " + clazz.getName());
        }

        this.localizedPageDefinitions.put(name, clazz);
    }

    /**
     * {@inheritDoc}
     */
    public IAttributePage getEmptyPage(final String pageName) {
        IAttributePage page;
        Class pageClass = pageDefinitions.get(pageName);
        if (pageClass == null) {
            pageClass = this.localizedPageDefinitions.get(pageName);
            if (pageClass == null) {
                throw new RuntimeException("Page type not defined: " + pageName);
            }
        }
        try {
            page = (IAttributePage) pageClass.newInstance();
        } catch (Exception e) {
            throw new RuntimeException("Failed to create page of type:" + pageName);
        }

        return page;
    }

    /**
     * {@inheritDoc}
     */
    public boolean isLocalizedPage(final String pageName) {
        return this.localizedPageDefinitions.containsKey(pageName);
    }

    /**
     * {@inheritDoc}
     */
    public abstract IPrimaryKey getPk(final String pkString);

    /**
     * {@inheritDoc}
     */
    public abstract IEntity get(final IPrimaryKey pk);

    /**
     * {@inheritDoc}
     */
    public IPrimaryKey create(final IEntity entity) {
        if (log.isDebugEnabled()) {
            log.debug("create called with: " + entity.getEntityHome().getEntityType());
        }
        entity.setDateCreated(getTimestamp());
        entity.setDateModified(getTimestamp());
        getHibernateTemplate().save(entity);

        return entity.getPk();
    }

    /**
     * {@inheritDoc}
     */
    public void update(final IEntity entity) {
        if (log.isDebugEnabled()) {
            log.debug("update called with: " + entity.getPk());
        }
        entity.setDateModified(getTimestamp());
        getHibernateTemplate().update(entity);
    }

    /**
     * {@inheritDoc}
     */
    public void save(final IEntity entity) {
        entity.setDateModified(getTimestamp());
        getHibernateTemplate().save(entity);
    }

    /**
     * {@inheritDoc}
     */
    public void saveOrUpdate(final IEntity entity) {
        if (log.isDebugEnabled()) {
            log.debug("saveOrUpdate called with: " + entity.getPk());
        }
        entity.setDateModified(getTimestamp());
        getHibernateTemplate().saveOrUpdate(entity);
    }

   /**
    * {@inheritDoc}
     */
    public <T> T merge(T entity) {
       return (T) getHibernateTemplate().merge(entity);
   }

    /**
     * {@inheritDoc}
     */
    public void delete(final IEntity entity) {
        getHibernateTemplate().delete(entity);
    }

    /**
     * {@inheritDoc}
     */
    public IEntity findByPk(final IPrimaryKey pk) {
        if (log.isDebugEnabled()) {
            log.debug("findByPk called with: " + pk);
        }
        return (IEntity) getHibernateTemplate().load(entityClass, pk);
    }

    /**
     * {@inheritDoc}
     */
    public IEntity getByPk(final IPrimaryKey pk) {
        if (log.isDebugEnabled()) {
            log.debug("getByPk called with: " + pk);
        }
        return (IEntity) getHibernateTemplate().get(entityClass, pk);
    }

    /**
     * Returns the entity with the given primary key string.
     * Uses <code>IEntityHome.getPk(String pkString)</code> to convert the given string
     * to an <code>IPrimaryKey</code>, then calls IEntityHome.getByPk(IPrimaryKey).
     *
     * @param pkString the string representation of the primary key
     * @return the entity with the specified key, or null if not found
     * @see org.marketlive.entity.IEntityHome#getPk(String)
     * @see org.marketlive.entity.IEntityHome#getByPk(org.marketlive.entity.IPrimaryKey)
     */
    public IEntity getByPk(final String pkString) {
        return getByPk(getPk(pkString));
    }

    /**
     * {@inheritDoc}
     */
    public IEntity findByPkLock(final IPrimaryKey pk) {
        log.debug("findByPk called with: " + pk);
        return (IEntity) getHibernateTemplate().load(entityClass, pk, LockMode.UPGRADE);
    }

    /**
     * {@inheritDoc}
     */
    public IEntity findByPk(final String pkString) {
        return findByPk(getPk(pkString));
    }

    /**
     * {@inheritDoc}
     */
    public Collection findAll() {
        return getHibernateTemplate().loadAll(entityClass);
    }

    /**
     * {@inheritDoc}
      */
     public Collection findByFieldMatch(String columnName, String value) {
         String query = "from " + entityClass.getName() + " where " + columnName + " = ?";
         return getHibernateTemplate().find(query, value);
     }

     /**
      * {@inheritDoc}
      */
     public Collection findByQuery(String pQueryString) {
         return getHibernateTemplate().find(pQueryString);
     }

    /**
     * Returns a <code>Timestamp</code> for the current time.
     * @return a <code>Timestamp</code> based on the current time
     */
    protected static final Timestamp getTimestamp() {
        return new Timestamp(System.currentTimeMillis());
    }

    /**
     * {@inheritDoc}
     */
    public Criteria createCriteria() {
        return (Criteria) getHibernateTemplate().execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws RuntimeException {
                try {
                    return session.createCriteria(entityClass);
                } catch (Exception e) {
                    throw new RuntimeException("Unable to extract entity criteria from session.", e);
                }
            }
        });
    }

    /**
     * {@inheritDoc}
     */
    public Iterator findByQueryIterate(String pQueryString, boolean useCache) {
        if (!useCache) {
            //disable interaction with second-level cache for this session
            getHibernateTemplate()
                .getSessionFactory()
                .getCurrentSession()
                .setCacheMode(CacheMode.IGNORE);
        }

        if (log.isDebugEnabled()) {
            log.debug("Cache mode: " + getHibernateTemplate().getSessionFactory().getCurrentSession().getCacheMode().toString());

        }

        return getHibernateTemplate().iterate(pQueryString);
    }

    /**
     * {@inheritDoc}
     */
    public void sessionClear(boolean gc) {
        getHibernateTemplate().clear();

        if (gc) {
            if (log.isDebugEnabled()) {
                log.debug("Calling System.gc() [current time in ms: " + System.currentTimeMillis());
            }
            System.gc();
        }
    }

    /**
     * {@inheritDoc}
     */
    public void sessionFlush() {
        getHibernateTemplate().flush();
    }
    
    /**
     * {@inheritDoc}
     */
    public boolean isDirty() {
    	return getHibernateTemplate().getSessionFactory().getCurrentSession().isDirty();
    }
 
    /**
     * {@inheritDoc}
     */
    public void updateCheckDirty(final IEntity entity) {
        if (log.isDebugEnabled()) {
            log.debug("update called with: " + entity.getPk());
        }
        if(isDirty()) { 
        	entity.setDateModified(getTimestamp());
        	getHibernateTemplate().update(entity);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void saveOrUpdateCheckDirty(final IEntity entity) {
        if (log.isDebugEnabled()) {
            log.debug("saveOrUpdate called with: " + entity.getPk());
        }
        if(entity.getPk() == null || isDirty()) { 
        	entity.setDateModified(getTimestamp());
        	getHibernateTemplate().saveOrUpdate(entity);
        }
    }
    
}
